// Have include guard.
#ifndef FPK_COMPOUNDFILTER_HPP
#define FPK_COMPOUNDFILTER_HPP

// Include files.
#include <vector>
#include <boost/smart_ptr/shared_ptr.hpp>
#include "../Internal/NullDeleter.hpp"
#include "Filter.hpp"
#include "CastFilter.hpp"

/**
 *  The CompoundFilter template class chains multiple Filters to act as a compounded filter.
 *  
 *  This is what happens when you apply this filter to an input object.
 *    - First, this will apply the added preconvert filters (filters that take an object of type Input and output an object of type Input).
 *    - Next, this will apply the conversion filter passed to the constructor (the filter that takes an object of type Input and outputs an object of type Output).
 *    - Finally, this will apply the added postconvert filters (filters that take an object of type Output and output an object of type Output).
 *  
 *  @tparam Input The type of the input object.
 *  @tparam Output The type of the output object.
 */
template<typename Input, typename Output = Input>
class CompoundFilter :
	public Filter<Input, Output>
{
	private:
		/**
		 *  Generate an appropriate CastFilter to convert from the Input type to the Output type.
		 *  
		 *  This is used when a conversion filter is not provided by the user.
		 *  
		 *  @return A CastFilter to convert from the Input type to the Output type.
		 */
		static boost::shared_ptr<const Filter<Input, Output> > GenerateCastFilter()
		{
			return boost::shared_ptr<const Filter<Input, Output> >
			(
				new CastFilter<Input, Output>()
			); 
		}
		
		std::vector<boost::shared_ptr<const Filter<Input> > > m_filtersPreconvert;
		boost::shared_ptr<const Filter<Input, Output> > m_filterConvert;
		std::vector<boost::shared_ptr<const Filter<Output> > > m_filtersPostconvert;
		
		/**
		 *  Set the conversion filter.
		 *  
		 *  @param filter A shared pointer to the desired conversion filter.
		 */
		void SetConvertFilter(boost::shared_ptr<const Filter<Input, Output> > filter)
		{
			m_filterConvert = filter;
		}
		
		/**
		 *  Set the conversion filter.
		 *  
		 *  @param filter A pointer to the desired conversion filter.
		 *  @param canDeleteFilter If the CompoundFilter can delete the Filter so long as it functions correctly.
		 */
		void SetConvertFilter(const Filter<Input, Output> *filter, bool canDeleteFilter = false)
		{
			if (canDeleteFilter)
				SetConvertFilter(boost::shared_ptr<const Filter<Input, Output> >(filter));
			else
				SetConvertFilter(boost::shared_ptr<const Filter<Input, Output> >(filter, NullDeleter()));
		}
		
		/**
		 *  Set the conversion filter.
		 *  
		 *  @param filter A reference to the desired conversion filter.
		 */
		void SetConvertFilter(const Filter<Input, Output> &filter)
		{
			SetConvertFilter(&filter);
		}
	public:
		/**
		 *  Constructor for CompoundFilter.
		 *  
		 *  @param filter A shared pointer to the conversion filter.
		 */
		CompoundFilter
		(
			boost::shared_ptr<const Filter<Input, Output> > filter =
				GenerateCastFilter()
		)
		{
			SetConvertFilter(filter);
		}
		
		/**
		 *  Constructor for CompoundFilter.
		 *  
		 *  @param filter A pointer to the conversion filter.
		 *  @param canDeleteFilter If the CompoundFilter can delete the Filter so long as it functions correctly.
		 */
		CompoundFilter(const Filter<Input, Output> *filter, bool canDeleteFilter = false)
		{
			SetConvertFilter(filter, canDeleteFilter);
		}
		
		/**
		 *  Constructor for CompoundFilter.
		 *  
		 *  @param filter A reference to the conversion filter.
		 */
		CompoundFilter(const Filter<Input, Output> &filter)
		{
			SetConvertFilter(filter);
		}
		
		/**
		 *  Destructor for CompoundFilter.
		 */
		virtual ~CompoundFilter()
		{
		}
		
		/**
		 *  Apply the added filters to an input.
		 *  
		 *  This will first apply the preconvert filters (filters that take an object of type Input and output an object of type Input).
		 *  Next, this will apply the conversion filter (the filter that takes an object of type Input and outputs an object of type Output).
		 *  Finally, this will apply the postconvert filters (filters that take an object of type Output and output an object of type Output).
		 *  
		 *  @param input A reference to the input (which will not get modified).
		 *  @return The output of the filtered input.
		 */
		virtual Output Apply(const Input &input) const
		{
			typename std::vector<boost::shared_ptr<const Filter<Input> > >::const_iterator itPreconvert;
			Input inputConvert = input;
			
			for (itPreconvert = m_filtersPreconvert.begin(); itPreconvert != m_filtersPreconvert.end(); ++itPreconvert)
				inputConvert = (*itPreconvert)->Apply(inputConvert);
			
			typename std::vector<boost::shared_ptr<const Filter<Output> > >::const_iterator itPostconvert;
			Output outputConvert = m_filterConvert->Apply(inputConvert);
			
			for (itPostconvert = m_filtersPostconvert.begin(); itPostconvert != m_filtersPostconvert.end(); ++itPostconvert)
				outputConvert = (*itPostconvert)->Apply(outputConvert);
			
			return outputConvert;
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  This overload adds a preconvert filter (one that is applied before the conversion filter).
		 *  
		 *  @param filter A shared pointer to the filter to be added.
		 */
		void AddFilter(boost::shared_ptr<const Filter<Input> > filter)
		{
			m_filtersPreconvert.push_back(filter);
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  This overload adds a preconvert filter (one that is applied before the conversion filter).
		 *  
		 *  @param filter A pointer to the filter to be added.
		 *  @param canDeleteFilter If the CompoundFilter can delete the Filter so long as it functions correctly.
		 */
		void AddFilter(const Filter<Input> *filter, bool canDeleteFilter = false)
		{
			if (canDeleteFilter)
				AddFilter(boost::shared_ptr<const Filter<Input> >(filter));
			else
				AddFilter(boost::shared_ptr<const Filter<Input> >(filter, NullDeleter()));
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  This overload adds a postconvert filter (one that is applied after the conversion filter).
		 *  
		 *  @param filter A shared pointer to the filter to be added.
		 */
		void AddFilter(boost::shared_ptr<const Filter<Output> > filter)
		{
			m_filtersPostconvert.push_back(filter);
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  This overload adds a postconvert filter (one that is applied after the conversion filter).
		 *  
		 *  @param filter A pointer to the filter to be added.
		 *  @param canDeleteFilter If the CompoundFilter can delete the Filter so long as it functions correctly.
		 */
		void AddFilter(const Filter<Output> *filter, bool canDeleteFilter = false)
		{
			if (canDeleteFilter)
				AddFilter(boost::shared_ptr<const Filter<Output> >(filter));
			else
				AddFilter(boost::shared_ptr<const Filter<Output> >(filter, NullDeleter()));
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  @param filter A reference to the filter to be added.
		 */
		template<typename TFilter>
		void AddFilter(const TFilter &filter)
		{
			AddFilter(&filter);
		}
};

/**
 *  The CompoundFilter template class chains multiple Filters to act as a compounded filter.
 *  
 *  @tparam Value The type of the input and output object.
 */
template<typename Value>
class CompoundFilter<Value, Value> :
	public Filter<Value, Value>
{
	private:
		std::vector<boost::shared_ptr<const Filter<Value> > > m_filters;
	public:
		/**
		 *  Destructor for CompoundFilter.
		 */
		virtual ~CompoundFilter()
		{
		}
		
		/**
		 *  Apply the added filters to an input.
		 *  
		 *  @param input A reference to the input (which will not get modified).
		 *  @return The output of the filtered input.
		 */
		virtual Value Apply(const Value &value)
		{
			typename std::vector<boost::shared_ptr<const Filter<Value> > >::const_iterator it;
			Value valueConvert = value;
			
			for (it = m_filters.cbegin(); it != m_filters.cend(); ++it)
				valueConvert = (*it)->Apply(valueConvert);
			
			return valueConvert;
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  @param filter A shared pointer to the filter to be added.
		 */
		void AddFilter(boost::shared_ptr<const Filter<Value> > filter)
		{
			m_filters.push_back(filter);
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  @param filter A pointer to the filter to be added.
		 *  @param canDeleteFilter If the CompoundFilter can delete the Filter so long as it functions correctly.
		 */
		void AddFilter(const Filter<Value> *filter, bool canDeleteFilter = false)
		{
			if (canDeleteFilter)
				AddFilter(boost::shared_ptr<const Filter<Value> >(filter));
			else
				AddFilter(boost::shared_ptr<const Filter<Value> >(filter, NullDeleter()));
		}
		
		/**
		 *  Add a Filter.
		 *  
		 *  @param filter A reference to the filter to be added.
		 */
		template<typename TFilter>
		void AddFilter(const TFilter &filter)
		{
			AddFilter(&filter);
		}
};

#endif // #ifndef FPK_COMPOUNDFILTER_HPP
